#!/usr/bin/perl -w
use 5.010;
use warnings;
use Config;
use IO::Socket;
use IO::Socket::INET;

my $connections;        # handle to the sockets I'm listening to
my $data;            # what I received
my @ready;            # which sockets have pending data
my $flags;            # udp flags, passed along blindly
my $timeout      = 15;        # seconds to wait for new data to arrive

my $client_listen = IO::Socket::INET->new(
  Localhost=>'127.0.0.1',
  LocalPort=>59999,
  Proto=>"udp",
  #Listen=>5,
  Reuse=>1,
  #ReusePort=>1
) or die "Unable to listen to UDP port 127.0.0.1: $@";

$client_listen->recv($data, 65536,$flags);        # block for initial
$connections = new IO::Select($client_listen);

while (@ready = $connections->can_read($timeout)) {
    foreach my $connection (@ready) {
        $connection->recv($data, 65536, $flags);
        if ($connection == $client_listen) {
            display("client", $data);
        }
    }
}

sub display {
    my ($origin, $data) = @_;
    my ($j, $k);
    my $hexstring = c2x($data);
    my @data = ($data =~ /(.{4})/sg);
    my @hexdata = ($hexstring =~ /(.{8})/g);
    my $extra = length($data) % 4;

    if ($extra > 0) {
        ($data[$#data+1]) = ($data =~ /(.{$extra})$/);
        $extra *= 2;
        ($hexdata[$#hexdata+1]) = ($hexstring =~ /(.{$extra})$/);
    }
    print "[Client";
    ($origin eq "client") ? print " => " : print " <= ";
    print "Server (" . length($data) . " bytes) " . ISO_time() . "]\n";

    for ($j = 0;$j <= $#data;$j+=4) {
        print n2x($j *4) .": ";
        $k = (($j + 3) < $#data) ? ($j +3) : $#data;

        print "$_ " foreach @hexdata[$j..$k];

        if ($k == $#data) {        # last line
            my $leftover;
            foreach (@hexdata[$j..$k]) {
                $leftover .= "$_ ";
            }
            my $spaces = 36 - length($leftover);

            print " "x$spaces;    # make following text line up nicely
        }
        print "   ";

        foreach (@data[$j..$k]) {
            $_ =~ tr/[ -~]/./c;    # handle unprintables
            print "$_ ";
        }
        print "\n";
    }
    print "\n";
}

# Convert a character string to a hex string
#
sub c2x {
    return unpack('H*', pack('a*', @_))
}

# Convert an integer number to a hex string
#
sub n2x {
    return unpack('H*', pack('n*', @_))
}

# Convert a Unix epoch timestamp to ISO 8601 standard format
# or return an ISO 8601 format timestamp for the current time
#
sub ISO_time {
    my ($second, $minute, $hour, $day, $month, $year) =
        ($_[0]) ? (localtime($_[0]))[0..5] : (localtime())[0..5];

    return sprintf("%4d-%02d-%02d %02d:%02d:%02d",
            $year+1900, $month+1, $day, $hour, $minute, $second);
}